import os from 'os'
import path from 'path'
import { Worker } from 'worker_threads'
import express from 'express'

import log from './log'
import PROJECT from '../PROJECT'

const WORKER = path.resolve(__dirname, 'renderMiddlewareWorker.js')
const QUEUE_SIZE = 256

class Queue {
  constructor() {
    this.queue = new Array(QUEUE_SIZE).fill(null)
    this.iEmpty = 0
    this.iExist = QUEUE_SIZE
  }

  value() {
    return this.iExist === QUEUE_SIZE ? null : this.queue[this.iExist]
  }

  putIn(req, res) {
    if (this.iEmpty === QUEUE_SIZE) {
      return res.status(500).send({ msg: 'too many requests!' })
    }
    this.queue[this.iEmpty] = { req, res }
    if (this.iExist === QUEUE_SIZE) { this.iExist = this.iEmpty }
    this.iEmpty = Queue.find(this.queue, this.iEmpty, true)
  }

  takeRequest() {
    if (this.iExist === QUEUE_SIZE) { return null }
    const el = this.queue[this.iExist]
    const req = el.req
    const i = this.iExist
    el.req = null
    this.iExist = Queue.find(this.queue, this.iExist, false)
    return { req, i }
  }

  takeResponseAt(i) {
    const res = this.queue[i].res
    this.queue[i] = null
    if (this.iEmpty === QUEUE_SIZE) { this.iEmpty = i }
    return { res, i }
  }

  static next(i) {
    return (i + 1 === QUEUE_SIZE ? 0 : i + 1)
  }

  static prev(i) {
    return (i - 1 < 0 ? QUEUE_SIZE - 1 : i - 1)
  }

  static find(queue, i, isFindEmpty) {
    const end = i
    i = Queue.next(i)
    while (i !== end) {
      const el = queue[i]
      if (isFindEmpty && el === null) { return i }
      if (!isFindEmpty && el !== null && el.req !== null) { return i }
      i = Queue.next(i)
    }
    return QUEUE_SIZE
  }
}

class Workers {
  constructor(queue) {
    this.queue = queue
    this.workers = Workers.create(queue)
  }

  value() { return this.workers }

  beBusy() {
    this.workers.map(worker => {
      if (worker.waiting === 0) {
        Workers.addWork(worker, this.queue)
      } 
    })
  }

  terminate() {
    this.workers.map(worker => worker.instance.terminate())
  }

  static finishWork(worker, queue, msg) {
    worker.waiting--
    const { output, i } = msg
    const { res } = queue.takeResponseAt(i)
    res.send(output)
  }

  static addWork(worker, queue) {
    const el = queue.takeRequest()
    if (el) {
      worker.waiting++
      const { req, i } = el
      worker.instance.postMessage({
        input: { path: req.path, originalUrl: req.originalUrl, query: req.query },
        i
      })
    }
  }

  static create(queue) {
    const env = {
      MODE_WORKER: 'yes',
      PROJECT: JSON.stringify(PROJECT),
    }
    const workers = os.cpus().slice(0, -1).map(() => {
      const worker = {
        instance: new Worker(WORKER, { env }),
        waiting: 0
      }

      worker.instance.on('message', msg => {
        Workers.finishWork(worker, queue, msg)
        Workers.addWork(worker, queue)
      })
      return worker
    })
    log.info(`created ${workers.length} workers`)
    return workers
  }

}

const renderMiddleware = () => {
  const router = express.Router()
  const queue = new Queue()
  const workers = new Workers(queue)

  router.use((req, res) => {
    queue.putIn(req, res)
    workers.beBusy()
  })

  return router
}

export default renderMiddleware
export {
  Queue,
  Workers,
}